/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package mathma.core.vector;

import mathma.core.expr.Expression;

import mathma.core.simp.Simplifier;

import mathma.core.value.Value;
import mathma.core.value.ValueType;

import java.util.Iterator;
import java.util.List;

/**
 * An abstract vector.  A vector consists of an ordered list of components.  A
 * Vector object can contain any non-zero number of components, althoug most
 * operations only work on equally-sized vectors.
 * 
 * @since 0.2.6
 * @author Ethan Levine
 * @param <E> The Value class that backs this Vector.
 */
public abstract class Vector<E extends Value> extends Value<Vector<E>>
        implements Iterable<Expression<E>> {

    // TODO implement vector simplifier.
    public final static Simplifier Simplifier = null;

    /**
     * A generic, non-parameterized ValueType for Vectors.  This cannot be
     * parameterized because parameters are non-static, so if a parameterized
     * type is needed, you should either create it yourself or cast this into
     * the proper type.
     */
    public final static ValueType GenericType =
            new ValueType(Simplifier, Vector.class);

    /**
     * The components of the vector.
     */
    private final Expression<E>[] components;

    /**
     * The size of the vector.
     */
    private final int size;

    /**
     * Creates a new Vector with a variable number of components.
     *
     * @param comps the components of the new vector.
     */
    protected Vector(Expression<E>... comps) {
        super(GenericType);
        size = comps.length;
        components = comps;
    }

    /**
     * Creates a new Vector with a list of components.
     *
     * @param comps the components of the new vector.
     */
    protected Vector(List<Expression<E>> comps) {
        this((Expression<E>[]) comps.toArray());
    }

    /**
     * Returns a specific component of this Vector.  This returns the component
     * with the index specified by the argument.  This index includes 0.  For
     * example, to get the second component of a 3-component vector, you would
     * call getComponent(1).
     *
     * @param comp the component to return.
     *
     * @return the specified component of this vector.
     */
    public final Expression<E> getComponent(int comp) {
        if (comp < size) {
            return components[comp];
        } else {
            // TODO-- calculation exception...
            throw new RuntimeException();
        }
    }

    /**
     * Gets the number of components in this vector.
     *
     * @return the size of this vector.
     */
    public final int getSize() {
        return size;
    }

    @Override
    public boolean equals(Object other) {
        if (Vector.class.isAssignableFrom(other.getClass())) {
            Vector v = (Vector) other;
            if (v.getSize() == getSize()) {
                for (int x = 0; x < getSize(); x++) {
                    if (!getComponent(x).equals(v.getComponent(x))) {
                        return false;
                    }
                }
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * Gets an iterator for this vector.  This iterator iterates through all the
     * components of this vector, in order.  If you try to call remove() on the
     * iterator, it throws an exception.
     *
     * @return an iterator for this vector.
     */
    public final Iterator<Expression<E>> iterator() {
        return new VectorIterator();
    }

    /**
     * Adds this vector to another.  Vector addition consists of adding each
     * individual component and using the sums to create a new vector.
     *
     * @param other the vector to add to this one.
     *
     * @return the sum of the two vectors.
     */
    abstract public Vector<E> add(Vector<E> other);

    /**
     * Subtracts another vector from this one.  Vector subtraction consists of
     * subtracting each individual component and using the differences to
     * create a new vector.
     *
     * @param other the vector to subtract from this one.
     * 
     * @return the difference of the two vectors.
     */
    abstract public Vector<E> subtract(Vector<E> other);

    /**
     * Multiplies this vector by a scalar.  Scalar multiplication consists of
     * multiplying each individual component by the same scalar and using the
     * products to create a new vector.
     *
     * @param scalar the scalar to multiply by.
     *
     * @return the product of a scalar and this vector.
     */
    abstract public Vector<E> scalarProduct(E scalar);

    /**
     * Finds the magnitude of this vector.  The magnitude of a vector is the
     * length of the vector in space.  This is generally the square root of the
     * sum of the squares of each component.
     *
     * @return the magnitude of this vector.
     */
    abstract public E magnitude();

    /**
     * Finds the dot product of this vector and another.  The dot product of two
     * vectors is the sum of the products of each component.  For example,
     * {1, 2, 3} * {4, 5, 6} = 1*4 + 2*5 + 3*6 = 32.  It is important to note
     * that the dot product of two vectors is a scalar, not another vector.
     *
     * @param other the vector to find the dot product with.
     *
     * @return the dot product of the two vectors.
     */
    abstract public E dotProduct(Vector<E> other);

    /**
     * Finds the cross product of this vector and another.  The cross product of
     * two vectors is a special third vector that is perpendicular to both of
     * the first two vectors and has a magnitude equal to the area of the
     * parallelogram formed by the operand vectors in space.
     *
     * @param other the vector to find the cross product with.
     *
     * @return the cross product of the two vectors.
     */
    abstract public Vector<E> crossProduct(Vector<E> other);

    /**
     * Finds the angle between this vector and another.  More specifically, this
     * finds the angle made by two vectors when their tails are matched to each
     * other.
     *
     * @param other the vector to find the angle with.
     *
     * @return the angle between the two vectors.
     */
    abstract public E angleWith(Vector<E> other);

    /**
     * Finds the unit vector that points in the same direction as this vector.
     * A unit vector is a vector which has a magnitude of 1.
     *
     * @return the unit vector that points in the direction of this vector.
     */
    abstract public Vector<E> unitVector();

    /**
     * Finds the projection of this vector along another.  This is the component
     * of this vector that points in the same direction as the other one.
     *
     * @param other the vector to project along.
     *
     * @return this vector's projection along the other one.
     */
    abstract public Vector<E> projectionAlong(Vector<E> other);

    /**
     * Finds if this vector is parallel to another.  Two vectors are parallel if
     * their unit vectors are the same.  In other words, two vectors are
     * parallel if they point in the same direction.
     *
     * @param other the vector to test parallelism with.
     *
     * @return whether or not the two vectors are parallel.
     */
    abstract public boolean isParallelTo(Vector<E> other);

    /**
     * Finds if this vector is antiparallel to another.  Two vectors are
     * antiparallel if their unit vectors are exact negations of each other.  In
     * other words, two vectors are antiparallel if they point in exactly
     * opposite directions.
     *
     * @param other the vector to test antiparallelism with.
     *
     * @return whether or not the two vectors are antiparallel.
     */
    abstract public boolean isAntiparallelTo(Vector<E> other);

    /**
     * Finds if this vector is perpendicular to another.  Two vectors are
     * perpendicular, or orthogonal, if their dot product is 0.  This means that
     * the zero vector is perpendicular to all vectors.
     *
     * @param other the vector to test perpendicularity with.
     *
     * @return whether or not the two vectors are perpendicular.
     */
    abstract public boolean isPerpendicularTo(Vector<E> other);

    private class VectorIterator implements Iterator<Expression<E>> {

        private int pos;

        public VectorIterator() {
            pos = 0;
        }

        public boolean hasNext() {
            return pos < getSize();
        }

        public Expression<E> next() {
            return getComponent(pos);
        }

        public void remove() {
            throw new UnsupportedOperationException(
                    "remove() not supported on vector iterators.");
        }
    }
}
